﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading.Tasks;
using Von.Library;
using Von.Web.Framework;
using Von.Web.Html;
using Von.Web.Models;
using Von.Web.Tasks;

namespace Von.Web
{
    /// <summary>
    /// 控制类型
    /// </summary>
    public enum EControlKind
    {
        /// <summary>
        /// 不控制功能
        /// </summary>
        CK_None,
        /// <summary>
        /// 视图类型（界面）
        /// </summary>
        CK_View,
        /// <summary>
        /// 系统菜单
        /// </summary>
        CK_Menu,
        /// <summary>
        /// ajax服务功能
        /// </summary>
        CK_Ajax,
        /// <summary>
        /// 系统功能服务
        /// </summary>
        CK_Service,
        /// <summary>
        /// 管理员级功能
        /// </summary>
        CK_Admin,
        /// <summary>
        /// 系统平台级功能
        /// </summary>
        CK_Host
    }
    #region "Attributes"
    /// <summary>
    /// 功能特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class ActionAttribute : Attribute
    {
        /// <summary>
        /// 功能唯一编码
        /// </summary>
        public Guid ID { get; set; }
        /// <summary>
        /// 功能名称
        /// </summary>
        public string ActionName { get; set; }
        /// <summary>
        /// 模块名称
        /// </summary>
        public string ModuleName { get; set; }
        /// <summary>
        /// 功能类型
        /// </summary>
        public EControlKind ControlKind { get; set; }
        /// <summary>
        /// 含参数构造函数
        /// </summary>
        /// <param name="GID">功能唯一编码</param>
        /// <param name="Description">功能名称</param>
        /// <param name="ControlKind">功能类型</param>
        public ActionAttribute(string ID, string ModuleName, string ActionName, EControlKind ControlKind)
        {
            this.ID = Guid.Parse(ID);
            this.ModuleName = ModuleName;
            this.ActionName = ActionName;
            this.ControlKind = ControlKind;
        }
    }
    /// <summary>
    /// 多语种资源特性
    /// </summary>
    /// <seealso cref="System.ComponentModel.DisplayNameAttribute" />
    public class LocalDisplay : DisplayNameAttribute
    {
        /// <summary>
        /// 资源名称
        /// </summary>
        /// <value>
        /// The name of the resource.
        /// </value>
        private string _resourceName;
        private string _key;
        /// <summary>
        /// <see cref="LocalDisplay"/> 的构造函数
        /// </summary>
        /// <param name="defaultName">The default name.</param>
        public LocalDisplay(string key, string resourceName)
        {
            _resourceName = resourceName;
            _key = key;
        }
        /// <summary>
        /// Gets the display name.
        /// </summary>
        /// <value>
        /// The display name.
        /// </value>
        public override string DisplayName
        {
            get
            {
                //var user = HttpContext
                //if (user == null) return _key;
                return _key;
            }
        }
    }
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class AuthorizeCodeAttribute : Attribute, IAsyncAuthorizationFilter
    {
        public string AppName { get; set; }
        public EControlKind ControlKind { get; set; }
        public string UsingName { get; set; }
        /// <summary>
        /// 设置功能点名称
        /// </summary>
        /// <param name="AppName">应用名称</param>
        /// <param name="ControlKind">控制类型</param>
        /// <param name="UsingName">功能名称</param>
        public AuthorizeCodeAttribute(string appName, EControlKind controlKind, string usingName)
        {
            AppName = appName;
            ControlKind = controlKind;
            UsingName = usingName;
        }

        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            if (!context.HttpContext.User.HasClaim(c => c.Value == AppName))
            {
                context.Result = new ForbidResult();
            }
            await Task.CompletedTask;
        }
    }
    public class VonJsonFloatConverer : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(double) || objectType == typeof(float) || objectType == typeof(decimal);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return reader.Value;
            //throw new NotImplementedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (value is double)writer.WriteValue(((double)value).ToString("F3"));
            if (value is float) writer.WriteValue(((float)value).ToString("F3"));
            if (value is decimal) writer.WriteValue(((decimal)value).ToString("F3"));
        }
    }
    public class VonJsonDateConverer : DateTimeConverterBase
    {
        private static IsoDateTimeConverter dtConvertor = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd" };

        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
        {
            dtConvertor.WriteJson(writer, value, serializer);
        }

        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            return dtConvertor.ReadJson(reader, objectType, existingValue, serializer);
        }
    }
    public class VonJsonTimeConverer : DateTimeConverterBase
    {
        private static IsoDateTimeConverter dtConvertor = new IsoDateTimeConverter { DateTimeFormat = "HH:mm:ss" };

        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
        {
            dtConvertor.WriteJson(writer, value, serializer);
        }

        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            return dtConvertor.ReadJson(reader, objectType, existingValue, serializer);
        }
    }
    public class VonJsonDatetimeConverer : DateTimeConverterBase
    {
        private static IsoDateTimeConverter dtConvertor = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };

        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
        {
            dtConvertor.WriteJson(writer, value, serializer);
        }

        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            return dtConvertor.ReadJson(reader, objectType, existingValue, serializer);
        }
    }
    public class VonJsonDaytimeConverer : DateTimeConverterBase
    {
        private static IsoDateTimeConverter dtConvertor = new IsoDateTimeConverter { DateTimeFormat = "dd日HH:mm:ss" };

        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
        {
            dtConvertor.WriteJson(writer, value, serializer);
        }

        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
        {
            return dtConvertor.ReadJson(reader, objectType, existingValue, serializer);
        }
    }
    /// <summary>
    /// 处理错误信息
    /// </summary>
    public class ErrorHandleAttribute : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext filterContext)
        {
            //获取异常信息，入库保存
            var exception = filterContext.Exception;
            var controllerName = filterContext.RouteData.Values["controller"].ToString();
            var actionName = filterContext.RouteData.Values["action"].ToString();
            var msg = "系统异常：" + exception.Message;//$"出错位置:{controllerName}/{actionName}----出错时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}";
            Runtime.Log.Error(msg, exception);//记录异常日志到本地磁盘
            var actionAttr = (filterContext.ActionDescriptor as ControllerActionDescriptor).MethodInfo.GetCustomAttributes(typeof(ActionAttribute), true);
 
            if (actionAttr.Length > 0)
            {
                switch (((ActionAttribute)actionAttr[0]).ControlKind)
                {
                    case EControlKind.CK_Ajax: filterContext.Result = new JsonResult(new FailedResult() { Error=msg} ); break;
                    default:filterContext.Result = new RedirectResult("/Home/Error");break;
                }
            }
            base.OnException(filterContext);
        }
    }
    #endregion
}
 
public static class UrlHelperExtensions
{
    /// <summary>
    /// 根据功能名称转换为调用URL
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="actionCode">功能名称，支持含控制器和区域名称</param>
    /// <example>
    /// /Portal/Admin/ListTable -》 指定区域和控制器的调用方法    =》area=Portal, ctrl=Admin, act=ListTable
    /// Portal/Admin/ListTable  -》 指定区域和控制器的调用方法    =》area=Portal, ctrl=Admin, act=ListTable
    /// /Admin/ListTable        -》 无区域，指定控制器的调用方法  =》area="", ctrl=Admin, act=ListTable
    /// Admin/ListTable         -》 默认区域，指定控制器的调用方法=》area=调用者区域, ctrl=Admin, act=ListTable
    /// ListTable               -》 默认区域和控制器的调用方法    =》area=调用者区域, ctrl=调用者控制器, act=ListTable
    /// </example>
    /// <returns></returns>
    public static string Ajax(this IUrlHelper helper, string actionCode)
    {
        string area = "", ctrl = "", act = ""; // E: /Portal/Admin/ListTable E: /Home/Index E: Admin/ListTable E: ListTable 
        string[] actValues = actionCode.TrimEnd('/').Split('/');
        switch (actValues.Length)
        {
            case 4: area = actValues[1]; ctrl = actValues[2]; act = actValues[3]; break;    //  /Portal/Admin/ListTable
            case 3: area = actValues[0]; ctrl = actValues[1]; act = actValues[2]; break;    //  Portal/Admin/ListTable 或 /Admin/ListTable
            case 2: area = Convert.ToString(helper.ActionContext.RouteData.Values["area"]);
                ctrl = actValues[0] == "" ? helper.ActionContext.RouteData.Values["controller"].ToString() : actValues[0]; 
                act = actValues[1]; break;
            case 1:
                area = Convert.ToString(helper.ActionContext.RouteData.Values["area"]);
                ctrl = helper.ActionContext.RouteData.Values["controller"].ToString();
                act = actValues[0]; break;                                                  //  ListTable     
            default:
                Runtime.Log.Error("Error! Can not to get ajax url with " + actionCode); 
                throw new Exception("Error! Can not to get ajax url with " + actionCode);
        }
        foreach(var a in Von.Web.Framework.Runtime.ListApplication())
        {
            var action = a.Actions.FirstOrDefault(
                c => c.Area.Equals(area, StringComparison.OrdinalIgnoreCase) &&
                    c.Controller.Equals(ctrl, StringComparison.OrdinalIgnoreCase) &&
                    c.ActionName.Equals(act, StringComparison.OrdinalIgnoreCase)
                );
            if(action != null) return "/Service/Execute/" + action.ID.ToString();
        }
        Runtime.Log.Error("Error! Can not to get ajax url with " + actionCode);
        throw new Exception("Error! Can not to get ajax url with " + actionCode);
    }
    public static string Img(this IUrlHelper helper, string imageCode)
    {
        return "/Service/Images/" + imageCode;
    }
}
namespace Von.Web.Html
{
    public enum EImportSection { Head, BodyHeader, BodyTrailler, Footor }
}
public static class HtmlHelperExtensions
{
    /// <summary>
    /// 引入命令
    /// </summary>
    /// <param name="section">引入段落标识，Header,footer</param>
    /// <param name="name">引入内容标志</param>
    /// <param name="template">要引入的内容</param>
    /// <example>
    /// @Html.Import(&quot;header&quot;, &quot;zTreeStyle_css&quot;, @&lt;link rel=&quot;stylesheet&quot; href=&quot;/content/zTreeStyle.css&quot; type=&quot;text/css&quot;&gt;)
    /// </example>
    public static HtmlString Import(this IHtmlHelper htmlHelper, EImportSection section, string name, Func<object, HelperResult> template)
    {
        string key_Name = "von_" + section.ToString() + "_IMPORT_" + name.ToLower();
        var a = htmlHelper.ViewContext.HttpContext.Items.FirstOrDefault(v => v.Key.ToString().Equals(key_Name, StringComparison.OrdinalIgnoreCase));
        if (a.Key != null) return HtmlString.Empty;
        htmlHelper.ViewContext.HttpContext.Items[key_Name] = template;
        return HtmlString.Empty;
    }
    /// <summary>
    /// 提取引入内容
    /// </summary>
    /// <param name="htmlHelper"></param>
    /// <param name="section">段落名称</param>
    /// <returns>要引入的html内容</returns>
    public static HtmlString RenderImports(this IHtmlHelper htmlHelper, EImportSection section)
    {
        string sectionKey = "von_" + section.ToString() + "_IMPORT_";
        var lst = htmlHelper.ViewContext.HttpContext.Items.Where(v => v.Key.ToString().StartsWith(sectionKey, StringComparison.OrdinalIgnoreCase));
        foreach (var sct in lst)
        {
            var template = htmlHelper.ViewContext.HttpContext.Items[sct.Key] as Func<object, HelperResult>;
            if (template != null)
            {
                htmlHelper.ViewContext.Writer.Write(template(null));
            }
        }
        return HtmlString.Empty;
    }
    public static HtmlString RawExtend(this IHtmlHelper htmlHelper, string filename)
    {
        string f = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "wwwroot", "Extends", filename);
        if (System.IO.File.Exists(f))
            htmlHelper.ViewContext.Writer.Write(System.IO.File.ReadAllText(f));
        return HtmlString.Empty;
    }
    public static HtmlString DisplayFor<TModel, TResult>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression, string[] options)
    {
        foreach(string s in options)
        {
            if (s.StartsWith(expression.ToString() + "=")) htmlHelper.ViewContext.Writer.Write(s.Substring(s.IndexOf("=") + 1));
        }
        return HtmlString.Empty;
    }
}

namespace Von.Web.Route
{
    public static class VonRouteRuntime
    {
        private static VonRoute route = null;
        public static VonRoute Instance(IRouter target) {
            if (route == null && target != null) route = new VonRoute(target);
            return route;
        }
        public static async Task doAjaxService(RouteContext context, string urlTrailler)
        {
            Guid actionID = Guid.Parse(urlTrailler);
            if (await Runtime.checkRight(context.HttpContext.User, actionID))
            {
                foreach (var a in Von.Web.Framework.Runtime.ListApplication())
                {
                    var action = a.Actions.FirstOrDefault(c => c.ID.Equals(actionID));
                    if (action != null)
                    {
                        context.RouteData.Values["area"] = action.Area;
                        context.RouteData.Values["controller"] = action.Controller;
                        context.RouteData.Values["action"] = action.ActionName;
                        break;
                    }
                }
            }
            else
            {
                var s = context.HttpContext.User.FindFirstValue(System.Security.Claims.ClaimTypes.NameIdentifier);
                Guid userID = string.IsNullOrEmpty(s) ? Guid.Empty : Guid.Parse(s);
                Runtime.Log.ErrorFormat("Action {0} can not using by {1}", actionID.ToString(), userID.ToString());
            }
        }
        public static async Task doImageService(RouteContext context, string urlTrailler)
        {
            Guid ID = Guid.Parse(urlTrailler);
            ImageTask task = new ImageTask();
            context.Handler = async ctx =>
            {
                var response = ctx.Response;
                byte[] bytes = await task.GetImg(ID);
                await response.Body.WriteAsync(bytes, 0, bytes.Length);
            };
        }
        public static async Task doUploadService(RouteContext context, string urlTrailler)
        {
            context.Handler = async ctx => {
                var response = ctx.Response;
                FileStream fs = new FileStream(Directory.GetCurrentDirectory() + "\\wwwroot\\uploaded\\" + urlTrailler, FileMode.Open);
                byte[] bytes = new byte[fs.Length];
                fs.Read(bytes, 0, (int)fs.Length);
                await response.Body.WriteAsync(bytes, 0, bytes.Length);
                fs.Close();
            };
            //return await Task.CompletedTask;
            //context.RouteData.Values["url"] = "/wwwroot/images/beian.png";
        }
        [Authorize]
        public static async Task doUserIcon(RouteContext context, string urlTrailler)
        {
            Guid userID = Guid.Parse(urlTrailler);
            Tasks.IdentityUserTask task = new Tasks.IdentityUserTask();
            context.Handler = async ctx =>
            {
                //而不删除响应标题。可以用该方法处理错误情况。  
                ctx.Response.Headers.Clear();
                ctx.Response.Headers.Add("Content-Disposition", "attachment;filename=" + System.Web.HttpUtility.UrlEncode("UserIcon.png"));//为用户保存文件是显示的名称  
                byte[] bytes = await task.GetUserIcon(userID);
                if(bytes == null || bytes.Length == 0)
                {
                    using(FileStream fs = new FileStream(Directory.GetCurrentDirectory() + "\\wwwroot\\images\\guest.png", FileMode.Open))
                    {
                        bytes = new byte[fs.Length];
                        fs.Read(bytes, 0, (int)fs.Length);
                    }
                }
                await ctx.Response.Body.WriteAsync(bytes, 0, bytes.Length);
            };
        }
    }
    public delegate Task VonRouteEvent(RouteContext context, string urlTrailler);
    /// <summary>
    /// 系统路由管理，可以创建新的路由方案，同时方案内可以通过Add函数建立支持多种类型转向支持
    /// </summary>
    public class VonRoute : IRouter
    {
        private KeyValueObjectCollection _urls = new KeyValueObjectCollection();
        private readonly IRouter _mvcRoute;
        /// <summary>
        /// 添加新的路由转向
        /// </summary>
        /// <param name="key">关键词</param>
        /// <param name="routeSetting">路由设置</param>
        /// <param name="routeEvent">路由转向函数</param>
        /// <returns></returns>
        public VonRoute Add(string key, string routeSetting, VonRouteEvent routeEvent)
        {
            _urls.Add(new KeyValueObject(key, routeSetting, routeEvent));
            return this;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="target">目标路由句柄</param>
        public VonRoute(IRouter target)
        {
            _mvcRoute = target;
        }
        /// <summary>
        /// 接口实现
        /// </summary>
        public async Task RouteAsync(RouteContext context)
        {
            var eventName = _urls.FirstOrDefault(u => context.HttpContext.Request.Path.Value.StartsWith(u.Value, StringComparison.OrdinalIgnoreCase));
            if (eventName != null)
            {
                string urlTrailler = context.HttpContext.Request.Path.Value.Remove(0, eventName.Value.Length);
                await ((VonRouteEvent)eventName.Obj)(context, urlTrailler);
                await _mvcRoute.RouteAsync(context);
            }
        }
        /// <summary>
        /// 接口实现
        /// </summary>
        public VirtualPathData GetVirtualPath(VirtualPathContext context)
        {
            if (context.Values.ContainsKey("legacyUrl"))
            {
                var url = context.Values["legacyUrl"] as string;
                //if (_urls.Contains(url))
                //{
                //    return new VirtualPathData(this, url);
                //}
            }
            return null;
        }
    }
}
